from scipy.stats import lognorm
from scipy.stats import norm
from scipy.stats import truncnorm
from scipy.stats import pearsonr
from scipy.stats import zscore
from enum import Enum
from itertools import cycle
from plotly.subplots import make_subplots
from IPython.display import Image
import numpy as np
import random as rd
import math
import copy
import pandas as pd
import plotly
import plotly.express as px
import plotly.figure_factory as ff
import plotly.graph_objects as go
plotly.offline.init_notebook_mode()
# plots
colors = ["#A31314", "#2B6999", "#E37002", "#B2C613", "#51A9B0", "#88837D"]
palette = cycle(colors)
# print
new_line = '\n'
new_line_space = '\n' + ' '
def style_plot(fig):
layout = {
'plot_bgcolor': '#ffffff',
'paper_bgcolor': '#ffffff'
}
# Change grid color and axis colors
fig.update_xaxes(gridcolor='LightGray')
fig.update_yaxes(gridcolor='LightGray')
# set white background
fig.update_layout(layout)
# returns n values, normally distributed:
# mean: average value
# std: standard deviation
def get_normal(mean, std, n):
return np.random.normal(loc=mean, scale=std, size=n)
# returns a random value from a log-normal distribution with
# mean: average value
# std: standard deviation
def get_lognormal(mean, std):
mu = mean
sigma = std
a = 1 + (sigma / mu) ** 2
s = np.sqrt(np.log(a))
scale = mu / np.sqrt(a)
return math.floor(lognorm.rvs(s=s, scale=scale))
# returns the n-th percentile of a normal distribution with:
# mean: average value
# n: n-th percentile
# Ex: 95th percentile -> point which 95% of the numbers are below
def get_nth_percentile(std: float, mean: float, n: int):
return norm.ppf(n / 100.0, loc=mean, scale=std) # percent-point-function
# returns average of numeric values in a list
def average_value(values: list[int] or list[float]):
return sum(values) / len(values)
# returns:
# std_self - how little a bidder trusts his original value estimate
# std_others - how little a bidder trusts other people's bids as estimates
# ..the values are negatively linearly correlated
def calculate_stds(private_info, consensus_bias, desire_coef, risk_coef, std_private_values):
std_self_coef = average_value(
[1-max(0, private_info), 1-max(0, consensus_bias), max(0, desire_coef), max(0, risk_coef)])
std_others_coef = 1 - std_self_coef
std_self = std_self_coef * std_private_values
std_others = std_others_coef * std_private_values
return std_self, std_others
# returns distribution of bidder's belief of other people's values as list of floats
def get_value_belief_dist(private_value, std, no_bidders):
return get_normal(private_value, std, no_bidders)
# Updates a bidders belief set (his value and beliefs for other bidders' values) using:
# bidder: the bidder whose belief set should be updatet
# no_bidders: the total number of bidders in the auction
# time: current time of the auction (discrete counter where one bid = one time unit)
# all_bids: all bids placed to this point in the auction
def update_belief_set2(bidder, no_bidders, time, all_bids):
std_incoming = bidder.std_others
std_prior = bidder.std
n = time
# calculate new bidder value
std_post = math.sqrt(1 /
((1 / math.pow(std_prior, 2)) + (n / math.pow(std_incoming, 2))))
# calculate new (posterior) belief set parameters
mu_prior = bidder.curr_value
x_mean = average_value(list(map(lambda bid: bid.amount, all_bids)))
mean_post = ((1 / math.pow(std_prior, 2)) / ((1 / math.pow(std_incoming, 2)) + (1 / math.pow(std_prior, 2)))) * mu_prior + \
((n / math.pow(std_incoming, 2)) / ((1 / math.pow(std_incoming, 2)) +
(1 / math.pow(std_prior, 2)))) * x_mean
# update bidder attributes
bidder.curr_value = math.floor(mean_post)
bidder.std = std_post
# get new belief set for other bidders' values
bidder.value_belief_distribution = get_value_belief_dist(
bidder.curr_value, bidder.std, no_bidders)
# plots all bidder belief set distributions in one dist plot
def plot_belief_distributions(belief_sets, title):
hist_data = list(belief_sets)
group_labels = list(map(lambda x: str(x), range(0, len(belief_sets))))
fig = ff.create_distplot(hist_data, group_labels, show_hist=False,
show_rug=False, curve_type="kde", bin_size=50)
fig.update_layout(title=title)
img_bytes = fig.to_image(format="png", width=1400, height=700, scale=0.8)
Image(img_bytes)
# plots all bidder belief set distributions in one scatter graph
def plot_belief_distributions_scatter(belief_sets, title):
hist_data = list(belief_sets)
group_labels = list(map(lambda x: str(x), range(0, len(belief_sets))))
fig = px.scatter(hist_data, color=group_labels, opacity=0.4)
fig.update_traces(marker={'size': 10})
fig.update_layout(title=title, width=600, height=800)
img_bytes = fig.to_image(format="png", width=1400, height=700, scale=0.8)
Image(img_bytes)
class Auction:
def __init__(self, id, N, reserve, min_increment_coef = 0.01):
self.id = id
# static values
self.N = N # no. bidders
self.reserve = reserve # item reserve / auction estimate
self.bidders = None # bidders signed up for the auction
# minimum amount to increment from last bid
self.min_increment = math.floor(reserve * min_increment_coef)
# dynamic values
self.t = 0 # current time
self.curr_bid = None # current highest bid
self.all_bids = [] # all placed bids
def __str__(self) -> str:
attribute_strings = (
'id: ' + self.id + new_line_space +
'no. bidders: ' + str(self.N) + new_line_space +
'min_increment: ' + str(self.min_increment) + new_line_space +
'reserve: ' + str(self.reserve) + new_line
)
return (
'Auction(' + new_line_space +
attribute_strings +
')' + new_line
)
class Bidder:
def __init__(self, name, predef_value, std_self, std_others, value_belief_distribution):
self.name = name
self.predef_value = predef_value # bidder's estimated value of item pre-auction
self.curr_value = predef_value # bidders updated in-auction value
self.is_active = True # all bidders start active
self.no_bids_submitted = 0 # no bids submitted by bidder
# the maximum amount he will ever update his value to (95th percentile)
self.max_raise = get_nth_percentile(std_self, predef_value, 95)
# what he thinks other bidder's values are
self.value_belief_distribution = value_belief_distribution
# --- std coefficients ---
# static
self.std_self = std_self # how much bidder trusts his original value estimate
self.std_others = std_others # how much the bidder trusts incoming information
# dynamic
self.std = std_self # how much bidder trusts his current value estimate
def __str__(self) -> str:
attribute_strings = (
'name: ' + self.name + new_line_space +
'predef_value: ' + str(self.predef_value) + new_line_space +
'curr_value: ' + str(self.curr_value) + new_line_space +
'max_raise: ' + str(self.max_raise) + new_line_space +
'std_self: ' + str(self.std_self) + new_line_space +
'std_others: ' + str(self.std_others) + new_line_space +
'std: ' + str(self.std) + new_line_space +
'is_active: ' + str(self.is_active) + new_line
)
return (
'Bidder(' + new_line_space +
attribute_strings +
')' + new_line
)
class Bid:
def __init__(self, amount: int, bidder: Bidder):
self.amount = amount # amount of bid
self.bidder = bidder # bidder that placed the bid
def __str__(self) -> str:
return 'Bid(amount=' + str(self.amount) + ' ,bidder=' + str(self.bidder) + ')'
# returns a bidders bid given
# curr_bid: the current highest bid
# curr_time: time passed in the auction
# bidder: bidder in question
# no_bidders: total no. bidders in the auction
# min_increment: minimum increment from current highest bid
def get_bidder_bid(curr_bid: Bid, curr_time: int, bidder: Bidder, no_bidders: int, min_increment: int):
bid_amount = 0
# bidder has reached his maximum coming in to the auction
# OR
# current bid is higher than his current estimated value
if ((curr_bid.amount > bidder.max_raise) | (curr_bid.amount > bidder.curr_value)):
# bidder opts out of the auction and becomes inactive
bidder.is_active = False
return 0
# bidder does not own the current highest bid AND the value + min_increment is still lower than his current value
if ((curr_bid.bidder != bidder) & ((curr_bid.amount + min_increment) < bidder.curr_value)):
# bid random on range [current bid + min_increment, value]
bid_amount = rd.randint(
curr_bid.amount + min_increment, bidder.curr_value)
return bid_amount
# runs the simulation of a single auction until only one bidder remains
def run_auction(auction):
# start at time=0 with no bids placed
auction.curr_bid = Bid(0, None)
auction.t = 0
no_more_bids = False
while (not no_more_bids):
bids = []
# collect bids from bidders that are still active
for bidder in auction.bidders:
if (bidder.is_active):
# get proposed bid from bidder
bid_amount = get_bidder_bid(
auction.curr_bid, auction.t, bidder, auction.N, auction.min_increment)
if (bid_amount > auction.curr_bid.amount):
bids.append(Bid(bid_amount, bidder))
if (len(bids) > 0):
# grab random bid out of the placed bids at time t and set as current bid
selected_bid = rd.choice(bids)
auction.curr_bid = selected_bid
auction.all_bids.append(selected_bid)
# update bidder no. bids
auction.curr_bid.bidder.no_bids_submitted += 1
# update each active bidder's belief set
for bidder in auction.bidders:
if ((bidder.is_active) & (auction.curr_bid.bidder != bidder)):
update_belief_set2(
bidder=bidder, no_bidders=auction.N, time=auction.t, all_bids=auction.all_bids)
else:
# no one wants to bid higher than current bid - end auction
no_more_bids = True
auction.t += 1
return auction.curr_bid
# runs n simulations of the auctioning of a lot with
# estimate: the auction house estimate pre-auction
# no_bidders: total no. bidders participating
# affiliation_coef: how affiliated bidder values are pre-auction
def run_simulation(no_iterations, estimate, no_bidders, affiliation_coef=0.05, min_increment_coef=0.01):
# standard deviation of bidder values - scaled to fit lot estimate
std = estimate * affiliation_coef
winning_bids = []
all_original_bidders = []
all_final_bidders = []
for i in range(0, no_iterations):
# create auction object
auction = Auction(id='b'+str(i+1), N=no_bidders,
reserve=estimate, min_increment_coef=min_increment_coef)
# create bidder objects
bidders = []
for i in range(0, auction.N):
bidder_private_value = get_lognormal(
mean=estimate, std=std)
bidder_private_info = get_normal(
mean=0.5, std=0.2, n=1)
bidder_consensus_bias = get_normal(
mean=0.5, std=0.2, n=1)
bidder_desire = get_normal(
mean=0.5, std=0.2, n=1)
bidder_risk_coef = get_normal(
mean=0.5, std=0.2, n=1)
std_self, std_others = calculate_stds(
bidder_private_info, bidder_consensus_bias, bidder_desire, bidder_risk_coef, std)
bidders.append(Bidder(
name='b'+str(i+1),
predef_value=bidder_private_value,
std_self=std_self[0],
std_others=std_others[0],
value_belief_distribution=get_value_belief_dist(bidder_private_value, std_self, auction.N)))
# store original bidder attributes
original_bidders = copy.deepcopy(bidders)
all_original_bidders.append(original_bidders)
auction.bidders = bidders
winning_bids.append(run_auction(auction))
all_final_bidders.append(auction.bidders)
return winning_bids, all_final_bidders, all_original_bidders
estimate = 1000
no_bidders = 10
winning_bids, all_final_bidders, all_original_bidders = run_simulation(
500, estimate, no_bidders)
all_original_values = []
for auction_bidders in all_original_bidders:
all_original_values.append(
list(map(lambda bidder: bidder.predef_value, auction_bidders)))
all_original_values = [
item for sublist in all_original_values for item in sublist]
fig = ff.create_distplot([all_original_values], group_labels=[
'Bidder Original Values'], show_rug=False)
fig.add_vline(x=np.median(all_original_values), line_width=1,
line_color=next(palette), annotation_text="Median (\"True\" value)", annotation_position="top")
fig.update_layout(title='Simulation: Distribution of Bidder Values at Auction Start', showlegend=False, xaxis_title='Bidder Values', yaxis_title='Density')
style_plot(fig)
img_bytes = fig.to_image(format="png", width=1400, height=700, scale=0.8)
Image(img_bytes)
# for i in range(0,len(all_original_bidders)):
# plot_belief_distributions_scatter(list(map(lambda bidder: bidder.value_belief_distribution,
# all_original_bidders[i])), 'Bidder\'s (Original) Belief Distributions')
# plot_belief_distributions_scatter(list(map(lambda bidder: bidder.value_belief_distribution,
# all_final_bidders[i])), 'Bidder\'s (Final) Belief Sets')
# for i in range(0,len(all_original_bidders)):
# plot_belief_distributions(list(map(lambda bidder: bidder.value_belief_distribution,
# all_original_bidders[i])), 'Bidder\'s (Original) Belief Distributions')
# plot_belief_distributions(list(map(lambda bidder: bidder.value_belief_distribution,
# all_final_bidders[i])), 'Bidder\'s (Final) Belief Distributions')
auction_results = []
for i in range(0, len(winning_bids)):
winning_bidder = winning_bids[i].bidder
all_but_winner = filter(lambda bidder: bidder.name !=
winning_bidder.name, all_original_bidders[i])
average_loser_value = average_value(
list(map(lambda losing_bidder: losing_bidder.curr_value, all_but_winner)))
winner_curse = average_loser_value - winning_bids[i].amount
auction_result = {
'winner_curse': winner_curse,
'winner_amount': winning_bids[i].amount,
'winner_utility': winning_bids[i].bidder.curr_value - winning_bids[i].amount,
'winner_no_bids_submitted': winning_bids[i].bidder.no_bids_submitted,
'winner_std': winning_bids[i].bidder.std,
'winner_std_self': winning_bids[i].bidder.std_self,
'paid_above_reserve': winning_bids[i].amount > estimate
}
auction_results.append(auction_result)
df = pd.DataFrame(auction_results)
fig = px.scatter(df, x="winner_amount", y="winner_curse",
opacity=0.3, title='Simulation: Average Loser Value vs. Amount Paid', color='paid_above_reserve', color_discrete_sequence=colors)
fig.update_traces(marker_size=5)
fig.update_xaxes(title='Amount Paid for Item')
fig.update_yaxes(title='Average Loser Value - Amount Paid')
style_plot(fig)
img_bytes = fig.to_image(format="png", width=1400, height=700, scale=0.8)
Image(img_bytes)
fig = px.scatter(df, x="winner_amount", y="winner_utility", trendline='ols',
opacity=0.3, title='Simulation: Winner Utility vs. Amount Paid', color_discrete_sequence=sorted(colors))
fig.update_traces(marker_size=5)
fig.update_xaxes(title='Amount Paid for Item')
fig.update_yaxes(title='Winner Value - Amount Paid')
style_plot(fig)
img_bytes = fig.to_image(format="png", width=1400, height=700, scale=0.8)
Image(img_bytes)
# ---WINNING BIDDER STD_SELF vs. STD_FINAL---
fig = px.scatter(df, x="winner_std", y="winner_std_self",
opacity=0.3, title='Simulation: Winner Initial vs. Final Distrust', color_discrete_sequence=colors)
fig.update_traces(marker_size=5)
fig.update_xaxes(title='Final Disrust in Estimate')
fig.update_yaxes(title='Initial Disrust in Own Estimate')
style_plot(fig)
img_bytes = fig.to_image(format="png", width=1400, height=700, scale=0.8)
Image(img_bytes)
# ---WINNING BIDDER NO BIDS SUBMITTED---
fig = px.histogram(map(lambda no: str(
no), df['winner_no_bids_submitted'].sort_values()), color_discrete_sequence=colors[2:])
fig.update_xaxes(title='No. Bids Submitted')
fig.update_yaxes(title='Count')
fig.update_layout(title='Simulation: No. Bids Submitted by Winner During Auction', showlegend=False)
style_plot(fig)
img_bytes = fig.to_image(format="png", width=1400, height=700, scale=0.8)
Image(img_bytes)
# ---WINNING BIDDER NO_BIDS vs. PRICE---
fig = px.scatter(df, y="winner_no_bids_submitted", x="winner_amount",
opacity=0.3, title='Simulation: Price Paid vs. Winner No. Bids Submitted', color_discrete_sequence=colors)
fig.update_traces(marker_size=5)
fig.update_yaxes(title='No. Bids Submitted')
fig.update_xaxes(title='Price Paid')
style_plot(fig)
img_bytes = fig.to_image(format="png", width=1400, height=700, scale=0.8)
Image(img_bytes)
df = pd.read_csv('auction_data_v2.csv')
df = df[df['number_of_bidders'].notnull()] # remove null bids
df = df[['auction_id', 'lot_id', 'low_estimate', 'number_of_bids', 'current_bid', 'paddle', 'number_of_bidders']]
agg_map = {'current_bid': ['count', 'max'], 'low_estimate': 'max'}
df_paddle_ids = df.groupby(['auction_id', 'lot_id', 'paddle']).agg(
agg_map).reset_index()
# unnest dataframe columns
df_paddle_ids.columns = df_paddle_ids.columns.map('_'.join)
df_paddle_ids = df_paddle_ids.reset_index()
df_paddle_ids = df_paddle_ids.rename(columns={'current_bid_count': 'bid_count', 'current_bid_max': 'bid_max'})
agg_map2 = {'bid_max': 'max'}
df_paddle_ids = df_paddle_ids[df_paddle_ids.groupby(['auction_id_', 'lot_id_'])['bid_max'].transform(max) == df_paddle_ids['bid_max']]
df_paddle_ids = df_paddle_ids.rename(columns={'auction_id_': 'auction_id', 'lot_id_': 'lot_id', 'paddle_': 'paddle', 'low_estimate_max': 'low_estimate'})
# add custom columns
df_paddle_ids['est_winbid_diff'] = df_paddle_ids['low_estimate'] - \
df_paddle_ids['bid_max']
df_paddle_ids['perc_above_estimate'] = (df_paddle_ids['bid_max'] -
df_paddle_ids['low_estimate']) / df_paddle_ids['low_estimate']
# remove outliers
df_paddle_ids_no_outliers = df_paddle_ids[(
np.abs(zscore(df_paddle_ids.select_dtypes(include=np.number))) < 3).all(axis=1)]
df_paddle_ids
| index | auction_id | lot_id | paddle | bid_count | bid_max | low_estimate | est_winbid_diff | perc_above_estimate | |
|---|---|---|---|---|---|---|---|---|---|
| 4 | 4 | -8948241167762729875 | -9196689798081083921 | 543.0 | 3 | 2400.0 | 2000.0 | -400.0 | 0.200000 |
| 9 | 9 | -8948241167762729875 | -9194564856127871092 | 918.0 | 5 | 1400.0 | 500.0 | -900.0 | 1.800000 |
| 12 | 12 | -8948241167762729875 | -9151072587192145600 | 822.0 | 1 | 900.0 | 1200.0 | 300.0 | -0.250000 |
| 13 | 13 | -8948241167762729875 | -9123971500861958519 | 714.0 | 2 | 300.0 | 500.0 | 200.0 | -0.400000 |
| 16 | 16 | -8948241167762729875 | -9102446430804671452 | 609.0 | 4 | 700.0 | 400.0 | -300.0 | 0.750000 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 11416 | 11416 | 3131899225618458582 | 2913979777108127063 | 1092.0 | 2 | 4000.0 | 4000.0 | 0.0 | 0.000000 |
| 11417 | 11417 | 3131899225618458582 | 3319739171358374966 | 525.0 | 1 | 1400.0 | 1500.0 | 100.0 | -0.066667 |
| 11418 | 11418 | 3131899225618458582 | 3341618596499002970 | 702.0 | 8 | 4800.0 | 3000.0 | -1800.0 | 0.600000 |
| 11421 | 11421 | 3131899225618458582 | 3377077189527049786 | 1026.0 | 1 | 50000.0 | 60000.0 | 10000.0 | -0.166667 |
| 11429 | 11429 | 3131899225618458582 | 3491731720948698270 | 621.0 | 6 | 2000.0 | 500.0 | -1500.0 | 3.000000 |
2903 rows × 9 columns
# ---WINNING BIDDER NO_BIDS vs. PRICE---
fig = px.scatter(df_paddle_ids_no_outliers, x="perc_above_estimate", y="bid_count",
opacity=0.3, title='Sotheby\'s: Price Paid Above Estimate vs. Winner No. Bids Submitted', color_discrete_sequence=colors)
fig.update_traces(marker_size=5)
fig.update_yaxes(title='No. Bids Submitted')
fig.update_xaxes(title='Percentage Paid Above Estimate')
style_plot(fig)
img_bytes = fig.to_image(format="png", width=1400, height=700, scale=0.8)
Image(img_bytes)
# returns second highest value in column
def second_max_func(x):
y = np.sort(x)
return y[-2] if len(y) > 1 else x
agg_map = {'current_bid': ['max', second_max_func], 'low_estimate': 'max',
'number_of_bids': 'max', 'number_of_bidders': 'max'}
df_lot_results = df.groupby(['auction_id', 'lot_id']).agg(
agg_map).reset_index().rename(columns={'current_bid': 'bids'})
# unnest dataframe columns
df_lot_results.columns = df_lot_results.columns.map('_'.join)
df_lot_results = df_lot_results.reset_index()
# rename columns
df_lot_results = df_lot_results.rename(columns={"bids_max": "winning_bid", "bids_second_max_func": "bids_second_max",
"low_estimate_max": "low_estimate", "number_of_bids_max": "number_of_bids", "number_of_bidders_max": "number_of_bidders"})
df_lot_results
| index | auction_id_ | lot_id_ | winning_bid | bids_second_max | low_estimate | number_of_bids | number_of_bidders | |
|---|---|---|---|---|---|---|---|---|
| 0 | 0 | -8948241167762729875 | -9196689798081083921 | 2400.0 | 2200.0 | 2000.0 | 15 | 7 |
| 1 | 1 | -8948241167762729875 | -9194564856127871092 | 1400.0 | 1300.0 | 500.0 | 11 | 3 |
| 2 | 2 | -8948241167762729875 | -9151072587192145600 | 900.0 | 800.0 | 1200.0 | 4 | 3 |
| 3 | 3 | -8948241167762729875 | -9123971500861958519 | 300.0 | 200.0 | 500.0 | 3 | 1 |
| 4 | 4 | -8948241167762729875 | -9102446430804671452 | 700.0 | 600.0 | 400.0 | 8 | 3 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 2831 | 2831 | 3131899225618458582 | 2913979777108127063 | 4000.0 | 3800.0 | 4000.0 | 3 | 1 |
| 2832 | 2832 | 3131899225618458582 | 3319739171358374966 | 1400.0 | 1400.0 | 1500.0 | 1 | 1 |
| 2833 | 2833 | 3131899225618458582 | 3341618596499002970 | 4800.0 | 4500.0 | 3000.0 | 11 | 3 |
| 2834 | 2834 | 3131899225618458582 | 3377077189527049786 | 50000.0 | 50000.0 | 60000.0 | 1 | 1 |
| 2835 | 2835 | 3131899225618458582 | 3491731720948698270 | 2000.0 | 2000.0 | 500.0 | 18 | 8 |
2836 rows × 8 columns
# add custom columns
df_lot_results['est_winbid_diff'] = df_lot_results['low_estimate'] - \
df_lot_results['winning_bid']
df_lot_results['paid_above_estimate'] = df_lot_results['winning_bid'] > \
df_lot_results['low_estimate']
df_lot_results['perc_above_estimate'] = (df_lot_results['low_estimate'] -
df_lot_results['winning_bid']) / df_lot_results['low_estimate']
df_lot_results['prop_max_secondmax_diff'] = (df_lot_results['winning_bid'] -
df_lot_results['bids_second_max']) / df_lot_results['winning_bid']
# remove outliers
df_lot_results_no_outliers = df_lot_results[(
np.abs(zscore(df_lot_results.select_dtypes(include=np.number))) < 3).all(axis=1)]
# --- Winning Bid vs. Low Estimate ---
fig = px.scatter(df_lot_results_no_outliers, x="low_estimate", y="winning_bid", color="paid_above_estimate",
opacity=0.3, title='Sotheby\'s: Winner\'s Curse?', log_x=True, log_y=True, color_discrete_sequence=colors)
fig.update_traces(marker_size=5)
fig.update_layout(yaxis_title='Winning Bid', xaxis_title='(Low) Estimate')
fig.update_layout(shapes=[{'type': 'line', 'y0': df_lot_results_no_outliers['winning_bid'].min(), 'y1': df_lot_results_no_outliers['winning_bid'].max(
), 'x0': df_lot_results_no_outliers['winning_bid'].min(), 'x1': df_lot_results_no_outliers['winning_bid'].max()}])
style_plot(fig)
img_bytes = fig.to_image(format="png", width=1400, height=700, scale=0.8)
Image(img_bytes)
# --- Winning Bid vs. Second-highest Bid ---
fig = px.scatter(df_lot_results_no_outliers, x="winning_bid", y="prop_max_secondmax_diff", color="paid_above_estimate",
opacity=0.3, title='Sotheby\'s: Winner\'s Curse?', log_x=True, color_discrete_sequence=colors)
fig.update_traces(marker_size=5)
fig.update_layout(xaxis_title='Winning Bid',
yaxis_title='Last Raise % by Winner')
style_plot(fig)
img_bytes = fig.to_image(format="png", width=1400, height=700, scale=0.8)
Image(img_bytes)
lots_gone_under_est = df_lot_results.loc[df_lot_results['winning_bid']
< df_lot_results['low_estimate']]
perc_lots_under_est = len(lots_gone_under_est) / len(df_lot_results)
print('Ratio of lots gone under low estimate: ',
format(perc_lots_under_est, '.2f'))
Ratio of lots gone under low estimate: 0.24
# --- No. Bidders vs. Amount Over Estimate ---
fig = px.scatter(df_lot_results_no_outliers, x="number_of_bidders", y="est_winbid_diff",
opacity=0.3, title='Sotheby\'s: No. Unique Bidders vs. Estimate - Winning Bid', trendline='ols')
fig.update_traces(marker_size=5)
fig.update_layout(xaxis_title='Number of Unique Bidders',
yaxis_title='Low Estimate - Winning Bid',
height=800,
width=1000)
style_plot(fig)
img_bytes = fig.to_image(format="png", width=1400, height=700, scale=0.8)
Image(img_bytes)
actual_results = []
simulation_averages = []
actual_simulation_diffs = []
for idx, lot_row in df_lot_results_no_outliers.iterrows():
estimate = lot_row['low_estimate']
no_bidders = lot_row['number_of_bidders']
winning_bids, all_final_bidders, all_original_bidders = run_simulation(
100, estimate, no_bidders)
simulation_avg_winning_bid = average_value(
list(map(lambda bid: bid.amount, winning_bids)))
actual_results.append(lot_row['winning_bid'])
simulation_averages.append(simulation_avg_winning_bid)
actual_simulation_diffs.append(
(abs(lot_row['winning_bid'] - simulation_avg_winning_bid) / lot_row['winning_bid']))
# --- Winning Bid - Actual vs. Simulation ---
fig = px.scatter(x=actual_results, y=simulation_averages, log_x=True, log_y=True,
opacity=0.3, title='Winning Bid - Actual vs. Simulation', color_discrete_sequence=colors)
fig.update_traces(marker_size=5)
fig.update_layout(xaxis_title='Actual Result (log)',
yaxis_title='Simulation Averages (log)')
style_plot(fig)
img_bytes = fig.to_image(format="png", width=1400, height=700, scale=0.8)
Image(img_bytes)
# --- Simulation error vs. winning price ---
fig = px.scatter(x=actual_results, y=actual_simulation_diffs, log_x=True,
opacity=0.3, title='Simulation Error vs. Winning Price', color_discrete_sequence=colors)
fig.update_traces(marker_size=5)
fig.update_layout(xaxis_title='Actual Result (log)',
yaxis_title='Simulation Error (%)')
style_plot(fig)
img_bytes = fig.to_image(format="png", width=1400, height=700, scale=0.8)
Image(img_bytes)
# statistic: measure of the strength and direction of association that exists between two variables measured
# pvalue: p-value (very low) suggests that the correlation coefficient is statistically significant,
# being much less than 0.01 (0.01 ---> the risk of concluding that a correlation exists when, actually,
# no correlation exists is 1%).
print('Actual vs. Estimate: ')
print(pearsonr(df_lot_results_no_outliers['winning_bid'],
df_lot_results_no_outliers['low_estimate']))
print('Actual vs. Simulation: ')
print(pearsonr(actual_results, simulation_averages))
print('Simulation vs. Estimate: ')
print(pearsonr(simulation_averages,
df_lot_results_no_outliers['low_estimate']))
Actual vs. Estimate: PearsonRResult(statistic=0.8827620288476193, pvalue=0.0) Actual vs. Simulation: PearsonRResult(statistic=0.9117663412355793, pvalue=0.0) Simulation vs. Estimate: PearsonRResult(statistic=0.9648693388967616, pvalue=0.0)
# --- Estimate error vs. winning price ---
estimate_diff_prop = abs(
(df_lot_results_no_outliers['winning_bid'] - df_lot_results_no_outliers['low_estimate'])) / df_lot_results_no_outliers.head(500)['winning_bid']
fig = px.scatter(x=actual_results, y=estimate_diff_prop, log_x=True,
opacity=0.3, title='Estimation error vs. winning price', color_discrete_sequence=colors)
fig.update_traces(marker_size=5)
fig.update_layout(xaxis_title='Winning Bid (log)',
yaxis_title='Estimation Error (%)')
style_plot(fig)
img_bytes = fig.to_image(format="png", width=1400, height=700, scale=0.8)
Image(img_bytes)
bidder_count_avgs = []
for bidder_count in range(2, 20):
winning_bids, all_final_bidders, all_original_bidders = run_simulation(
500, 10000, bidder_count)
simulation_avg_winning_bid = average_value(
list(map(lambda bid: bid.amount, winning_bids)))
bidder_count_avgs.append(
{
'bidder_count': bidder_count,
'winning_avg': simulation_avg_winning_bid
}
)
# --- No. Bidders vs. Winning Price ---
fig = px.scatter(bidder_count_avgs, x="bidder_count", y="winning_avg",
opacity=0.5, title='Simulation: Winner\'s Curse and Number of Biddders')
fig.update_traces(marker_size=5)
fig.update_layout(xaxis_title='Number of Bidders',
yaxis_title='Winning Bid',
height=600,
width=1000)
style_plot(fig)
img_bytes = fig.to_image(format="png", width=1400, height=700, scale=0.8)
Image(img_bytes)
price_avgs = []
for price in [10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000]:
winning_bids, all_final_bidders, all_original_bidders = run_simulation(
500, price, 10)
simulation_avg_winning_bid = average_value(
list(map(lambda bid: bid.amount, winning_bids)))
price_avgs.append(
{
'price': price,
'bid_med_diff': ((simulation_avg_winning_bid - price) / price) * 100
}
)
# --- Price vs. Winning Bid ---
fig = px.scatter(price_avgs, x="price", y="bid_med_diff",
opacity=0.5, title='Simulation: Winner\'s Curse and Item Price', log_x=True)
fig.update_traces(marker_size=5)
fig.update_layout(xaxis_title='Estimate (log)',
yaxis_title='Winning Bid Average % Increase',
height=600,
width=1000)
style_plot(fig)
img_bytes = fig.to_image(format="png", width=1400, height=700, scale=0.8)
Image(img_bytes)
price_avgs = []
original_values_aff = []
row = 0
fig = make_subplots(rows=5, shared_xaxes=True)
for affiliation in [0.0075, 0.01, 0.025, 0.05, 0.075, 0.1, 0.125, 0.15, 0.175, 0.2, 0.225, 0.25, 0.275, 0.3]:
winning_bids, all_final_bidders, all_original_bidders = run_simulation(
500, 10000, 10, affiliation_coef=affiliation)
simulation_avg_winning_bid = average_value(
list(map(lambda bid: bid.amount, winning_bids)))
all_original_values = []
for auction_bidders in all_original_bidders:
all_original_values.append(
list(map(lambda bidder: bidder.predef_value, auction_bidders)))
all_original_values = [
item for sublist in all_original_values for item in sublist]
price_avgs.append(
{
'affiliation': affiliation,
'winning_bid': simulation_avg_winning_bid
}
)
if (affiliation in [0.01, 0.05, 0.1, 0.15, 0.2]):
row += 1
fig.append_trace(go.Histogram(x=all_original_values,
histfunc='count', name=affiliation), row=row, col=1)
style_plot(fig)
fig.update_layout(width=1000, height=600,
title='Simulation: Bidder Value Distribution With Different Affiliation Coefficients')
fig.update_yaxes(showticklabels=False)
img_bytes = fig.to_image(format="png", width=1400, height=700, scale=0.8)
Image(img_bytes)
# --- Affiliation Coefficient vs. Winning Bid ---
fig = px.scatter(price_avgs, x="affiliation", y="winning_bid",
opacity=0.5, title='Simulation: Winner\'s Curse and Bidder Value Affiliation')
fig.update_traces(marker_size=5)
fig.update_layout(xaxis_title='Affiliation',
yaxis_title='Winning Bid',
height=600,
width=1000)
style_plot(fig)
img_bytes = fig.to_image(format="png", width=1400, height=700, scale=0.8)
Image(img_bytes)
price_avgs = []
price = 10000
for min_inc in [x * 0.001 for x in range(1,30)]:
winning_bids, all_final_bidders, all_original_bidders = run_simulation(
1000, price, 10, min_increment_coef=min_inc)
simulation_avg_winning_bid = average_value(
list(map(lambda bid: bid.amount, winning_bids)))
price_avgs.append(
{
'min_inc': min_inc,
'bid_med_diff': ((simulation_avg_winning_bid - price) / price) * 100
}
)
# --- Affiliation Coefficient vs. Winning Bid ---
fig = px.scatter(price_avgs, x="min_inc", y="bid_med_diff",
opacity=0.5, title='Simulation: Winner\'s Curse and Minimum Increment')
fig.update_traces(marker_size=5)
fig.update_layout(xaxis_title='Minimum Increment as % of Lot Estimate',
yaxis_title='Winning Bid Average % Increase from Estimate',
height=600,
width=1000)
style_plot(fig)
img_bytes = fig.to_image(format="png", width=1400, height=700, scale=0.8)
Image(img_bytes)